﻿using PSE.Framework.Communication.Configuration.Adapters.File;
using PSE.Framework.Communication.Adapters;
using System.IO;
using PSE.Framework.Communication.Configuration.Adapters;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Diagnostics;
using FileHelpers;
using PSE.Framework.Communication.Messages;

namespace PSE.Framework.Communication.Adapters.File
{
    public abstract class SyncFileInputAdapter<TConfig> : SyncStreamInputAdapter<TConfig, object, FileReceiveParameter>, ISyncFileInputAdapter
        where TConfig : FileInputAdapterConfiguration
    {
        #region Eventos
        public event EventHandler<File.FileCreatedEventArgs> NewFileCreated;
        #endregion

        #region Construtor
        public SyncFileInputAdapter()
        {
            _asyncFileInputAdapter = this.CreateAsyncFileInputAdapter();

            this.AsyncFileInputAdapter.NewFileCreated += new EventHandler<FileCreatedEventArgs>(AsyncFileInputAdapter_NewFileCreated);

            this.AsyncFileInputAdapter.StreamOpened += new EventHandler<PSE.Framework.Communication.Messages.Adapters.StreamOpenedEventArgs>(AsyncFileInputAdapter_StreamOpened);
            this.AsyncFileInputAdapter.MessageReceived += new EventHandler<MessageEventArgs>(AsyncFileInputAdapter_MessageReceived);
            this.AsyncFileInputAdapter.StreamClosed += new EventHandler<MessageEventArgs>(AsyncFileInputAdapter_StreamClosed);

            this.AsyncFileInputAdapter.StreamCanceled += new EventHandler<MessageEventArgs>(AsyncFileInputAdapter_StreamCanceled);
            this.AsyncFileInputAdapter.MessageReceivedError += new EventHandler<MessageReceivedErrorEventArgs>(AsyncFileInputAdapter_MessageReceivedError);

        }
        #endregion

        #region AsyncFileInputAdapter
        private IAsyncFileInputAdapter _asyncFileInputAdapter = null;

        protected IAsyncFileInputAdapter AsyncFileInputAdapter
        {
            get { return _asyncFileInputAdapter; }
        }

        protected abstract IAsyncFileInputAdapter CreateAsyncFileInputAdapter();
        #endregion

        private ManualResetEvent _waitOpenFile = new ManualResetEvent(false);
        private ManualResetEvent _waitReceiveMessage = new ManualResetEvent(false);
        private ManualResetEvent _waitReadMessage = new ManualResetEvent(false);

        private int _recordCount = -1;
        private int _currentIndex = -1;
        private bool _closed = true;

        private MessageInfo _message = null;
        private Exception _exception = null;

        public override void Open(TConfig config, object parameter)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            this.VerifyIsCreated();

            this.AsyncFileInputAdapter.Open(config, parameter);

            base.Open(config, parameter);
        }

        public override int OpenStream(FileReceiveParameter parameter)
        {
            this.VerifyIsOpened();
            this.VerifyStreamIsCreated();

            _waitOpenFile.Reset();
            _waitReceiveMessage.Reset();
            _waitReadMessage.Reset();

            _recordCount = -1;
            _currentIndex = -1;

            _message = null;
            _exception = null;

            _closed = false;

            if (this.AsyncFileInputAdapter.BeginReceive(parameter))
                _waitOpenFile.WaitOne();

            if (_recordCount > 0)
            {
                base.OpenStream(parameter);
            }
            else
            {
                _closed = true;

                this.AsyncFileInputAdapter.EndReceive();
            }

            if (_exception != null)
            {
                throw _exception;
            }

            return _recordCount;
        }

        void AsyncFileInputAdapter_NewFileCreated(object sender, FileCreatedEventArgs e)
        {
            if (NewFileCreated != null)
                NewFileCreated(this, e);
        }

        void AsyncFileInputAdapter_StreamOpened(object sender, PSE.Framework.Communication.Messages.Adapters.StreamOpenedEventArgs e)
        {
            _recordCount = e.MessageCount;

            _waitOpenFile.Set();
        }

        void AsyncFileInputAdapter_MessageReceived(object sender, MessageEventArgs e)
        {
            if (!_closed)
            {
                _waitReadMessage.WaitOne();
                _waitReadMessage.Reset();

                _message = new MessageInfo(e.Action, e.Message);
                _exception = null;

                _waitReceiveMessage.Set();
            }
        }

        void AsyncFileInputAdapter_MessageReceivedError(object sender, MessageReceivedErrorEventArgs e)
        {
            if (!_waitOpenFile.WaitOne(0))
            {
                _recordCount = -1;
                _message = null;
                _exception = e.Exception;

                _waitOpenFile.Set();
            }
            else if (!_closed)
            {
                _waitReadMessage.WaitOne();
                _waitReadMessage.Reset();

                _message = null;
                _exception = e.Exception;

                _waitReceiveMessage.Set();
            }
        }

        void AsyncFileInputAdapter_StreamClosed(object sender, MessageEventArgs e)
        {
            _message = null;
            _exception = null;

            _waitReceiveMessage.Set();
        }

        void AsyncFileInputAdapter_StreamCanceled(object sender, MessageEventArgs e)
        {
            _message = null;
            _exception = null;

            _waitReceiveMessage.Set();
        }

        public override MessageInfo Receive(FileReceiveParameter parameter)
        {
            if (_closed)
                return null;

            _currentIndex++;

            _waitReadMessage.Set();
            _waitReceiveMessage.WaitOne();

            MessageInfo message = _message;
            Exception exception = _exception;

            _waitReceiveMessage.Reset();

            if (_currentIndex >= _recordCount - 1)
            {
                this.CloseStream();
            }

            if (exception != null)
            {
                if (this.Configuration.MessageReceivedErrorBehavior == MessageReceivedErrorBehavior.Abort)
                {
                    this.CloseStream();
                }

                throw exception;
            }

            return message;
        }

        public override void CloseStream()
        {
            _closed = true;

            _waitOpenFile.Set();
            _waitReadMessage.Set();
            _waitReceiveMessage.Set();

            if (this.AsyncFileInputAdapter.CanReceive)
                this.AsyncFileInputAdapter.EndReceive();

            base.CloseStream();
        }

        public override void CancelStream()
        {
            _closed = true;

            _waitOpenFile.Set();
            _waitReadMessage.Set();
            _waitReceiveMessage.Set();

            this.AsyncFileInputAdapter.CancelStream();

            if (this.AsyncFileInputAdapter.CanReceive)
                this.AsyncFileInputAdapter.EndReceive();

            base.CancelStream();
        }

        public override void Close()
        {
            _closed = true;

            this.VerifyIsOpened();

            _waitOpenFile.Set();
            _waitReadMessage.Set();
            _waitReceiveMessage.Set();

            this.AsyncFileInputAdapter.Close();

            base.Close();
        }
    }
}
